//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include "pmatrix.h"
#include "skybox.h"

using namespace M5D;
using namespace PSYSGFX;
using namespace PMath;

//==================================================================
static const int	NDIV_PHI = 12;
static const int	NDIV_THETA = 28;
static const float	EARTH_DIAMETER_KM = 12750;
static const float	PI = 3.14159265f;
static const int	TOTAL_TRIGS = (NDIV_THETA+1)*(NDIV_PHI+1) * 2;

#define SCALE	0.2f

//==================================================================
SkyBox::SkyBox( M5D::Scene *scenep ) :
	_scenep(scenep),
	_skymeshp(NULL),
	_floormeshp(NULL),
	_skymatep(NULL),
	_floormatep(NULL)
{
	_sky_base_col.Set( 190, 230, 240 );
	_sky_high_col.Set( 5,  50, 240 );
	_floor_far_col.Set( 172, 155, 100 );
	_floor_near_col.Set( 172, 155, 100 );
	_floorslice_far_col.Set( 172, 155, 180 );
	_floorslice_near_col.Set( 172, 155, 180 );
	_floorslice_ang_from = 0;
	_floorslice_ang_to = 0;

	_skymatep = _scenep->NewMaterial( "Sky Mate" );
	_skymatep->SetWriteDepth( false );
	_floormatep = _scenep->NewMaterial( "Floor Mate" );
	_floormatep->SetWriteDepth( false );

	_skymeshp = _scenep->NewMesh( "Sky Mesh" );
	_floormeshp = _scenep->NewMesh( "Floor Mesh" );
}

//==================================================================
static float visible_radius_mt( float obs_heigth_mt )
{
float	r, h;

	r = (EARTH_DIAMETER_KM / 2.0f) * 1000.0f;
	h = obs_heigth_mt;
	return sqrt( (h + r)*(h + r) - r*r );
}

//=============================================================================
static void buildGeometry( Mesh *meshp, Material *matep,
							const Vector3 &phi_col0, const Vector3 &phi_col1,
							const Vector3 &phi_scol0, const Vector3 &phi_scol1,
							int slice_ang_from, int slice_ang_to,
							float scale_y, float grad_intpl_speed )
{
	Vector3	v00, v01, v10, v11;
	Vector3	old_col,	new_col;
	Vector3	old_scol,	new_scol;
	Color4b	old_col_c4, new_col_c4;

	float dome_r = visible_radius_mt( 1.72f );
	float dome_h = 2 * 1000;

	float y_base = dome_h  * sinf( PI/4 );

	M5D::DispList	&dlist = *meshp->OpenDispListVertex( PackVert::TYPE_POSCOL, TOTAL_TRIGS*3 );
	dlist.Reset();

	PackVert::PosCol	*verts_startp = dlist.BeginAddVertexPrimsPC( matep, Primitive::TYPE_TRIANGLELIST, TOTAL_TRIGS*3 );
	PackVert::PosCol	*build_vertsp = verts_startp;

	old_col = phi_col0;
	old_scol = phi_scol0;
	for (int i=0; i < NDIV_PHI; ++i)
	{
		float phi0 = PI/4 + (i+0) * (PI/4 / NDIV_PHI);
		float phi1 = PI/4 + (i+1) * (PI/4 / NDIV_PHI);

		float ring_radius0 = dome_r * cosf( phi0 );
		float ring_radius1 = dome_r * cosf( phi1 );

		float a = (float)i / NDIV_PHI * grad_intpl_speed;
		if ( a > 1 )	a = 1;
		new_col	 = phi_col0  * (1-a) + phi_col1  * a;
		new_scol = phi_scol0 * (1-a) + phi_scol1 * a;

		for (int j=0; j < NDIV_THETA+1; ++j)
		{
			float theta = j * (2*PI / NDIV_THETA);

			v10.Set( ring_radius0	* cosf( theta ),
					 dome_h			* sinf( phi0 ) - y_base,
					 ring_radius0	* sinf( theta ) );
			v10 *= SCALE;
			v10._y *= scale_y;

			v11.Set( ring_radius1	* cosf( theta ),
					 dome_h			* sinf( phi1 ) - y_base,
					 ring_radius1	* sinf( theta ) );
			v11 *= SCALE;
			v11._y *= scale_y;

			if ( j != 0 )
			{
				old_col_c4.Set( (int)(old_col._x),
								(int)(old_col._y),
								(int)(old_col._z), 255 );
				new_col_c4.Set( (int)(new_col._x),
								(int)(new_col._y),
								(int)(new_col._z), 255 );

				if ( slice_ang_from || slice_ang_to )
				{
					if ( j * 360 / NDIV_THETA >= slice_ang_from && j * 360 / NDIV_THETA <= slice_ang_to )
					{
						old_col_c4.Set( (int)(old_scol._x),
										(int)(old_scol._y),
										(int)(old_scol._z), 255 );
						new_col_c4.Set( (int)(new_scol._x),
										(int)(new_scol._y),
										(int)(new_scol._z), 255 );
					}
				}

				// 00
				build_vertsp->col = old_col_c4;
				build_vertsp->pos = v00;
				++build_vertsp;
				// 11
				build_vertsp->col = new_col_c4;
				build_vertsp->pos = v11;
				++build_vertsp;
				// 01
				build_vertsp->col = new_col_c4;
				build_vertsp->pos = v01;
				++build_vertsp;

				// 00
				build_vertsp->col = old_col_c4;
				build_vertsp->pos = v00;
				++build_vertsp;
				// 10
				build_vertsp->col = old_col_c4;
				build_vertsp->pos = v10;
				++build_vertsp;
				// 11
				build_vertsp->col = new_col_c4;
				build_vertsp->pos = v11;
				++build_vertsp;
			}

			v00 = v10;
			v01 = v11;
		}

		old_col = new_col;
		old_scol = new_scol;
	}

	dlist.EndAddVertexPrims();
	
	meshp->CloseDispListVertex();
}

//=============================================================================
void SkyBox::Rebuild()
{
	buildGeometry(	_skymeshp, _skymatep,
					_sky_base_col, _sky_high_col,
					_floorslice_far_col, _floorslice_near_col, 0,0, 1.0f, 1.0f );

	buildGeometry(	_floormeshp, _floormatep,
					_floor_far_col, _floor_near_col,
					_floorslice_far_col, _floorslice_near_col,
					_floorslice_ang_from, _floorslice_ang_to,  -0.2f, 1.5f );
}

//=============================================================================
void SkyBox::Update( const Vector3 &cam_pos )
{
	Matrix44	tramat;

	Vector3	cpos = cam_pos;// * -1;

	tramat = Matrix44::Tra( cpos );

	_skymeshp->SetMatrix( tramat );
	_floormeshp->SetMatrix( tramat );
}
